#include "napi/native_api.h"
#include <pjsua2.hpp>
#include <iostream>
#include "hilog/log.h"
#include "thirdparty/yyjson/yyjson.h"
#include "pjsip_manager.hpp"

using namespace std;
using namespace pj;

PJSUAManager *PJSUAManager::_instance = 0;

static void CallJs(napi_env env, napi_value jsCb, void *context, void *data) {
    Message *message = reinterpret_cast<Message *>(data);

    if (env == nullptr) {
        return;
    }
    CallbackData *context2 = reinterpret_cast<CallbackData *>(context);
    ;
    napi_value ret;
    napi_value undefined = nullptr;
    napi_status operStatus = napi_get_undefined(env, &undefined);
    if (operStatus != napi_ok) {
        return;
    }

    if (message->method == "log") {
        // 回调ArkTS函数
        napi_value callback = nullptr;
        napi_get_reference_value(env, context2->callbackRef, &callback);

        napi_value callbackArg[1] = {nullptr};
        operStatus =
            napi_create_string_utf8(env, message->value1.c_str(), message->value1.length(), &callbackArg[0]);
        if (operStatus != napi_ok) {
            return;
        }

        napi_call_function(env, undefined, callback, 1, callbackArg, &ret);
    } else if (message->method == "on_state_change") {
        // 回调ArkTS函数
        napi_value callback = nullptr;
        napi_get_reference_value(env, context2->stateCallbackRef, &callback);
        napi_value callbackArg[2] = {nullptr};
        operStatus = napi_create_string_utf8(env, message->value1.c_str(), message->value1.length(), &callbackArg[0]);
        if (operStatus != napi_ok) {
            return;
        }
        operStatus = napi_create_string_utf8(env, message->value2.c_str(), message->value2.length(), &callbackArg[1]);
        if (operStatus != napi_ok) {
            return;
        }
        napi_call_function(env, undefined, callback, 2, callbackArg, &ret);
    } else if (message->method == "onIncoming") {
        // 回调ArkTS函数
        napi_value callback = nullptr;
        napi_get_reference_value(env, context2->incomingCallbackRef, &callback);

        napi_value callbackArg[3] = {nullptr};
        operStatus = napi_create_string_utf8(env, message->value1.c_str(), message->value1.length(), &callbackArg[0]);
        if (operStatus != napi_ok) {
            return;
        }
        operStatus = napi_create_string_utf8(env, message->value2.c_str(), message->value2.length(), &callbackArg[1]);
        if (operStatus != napi_ok) {
            return;
        }
        operStatus = napi_create_string_utf8(env, message->value3.c_str(), message->value3.length(), &callbackArg[2]);
        if (operStatus != napi_ok) {
            return;
        }
        napi_call_function(env, undefined, callback, 3, callbackArg, &ret);
    } else if (message->method == "record") {
        // 回调ArkTS函数
        napi_value callback = nullptr;
        napi_get_reference_value(env, context2->recordCallbackRef, &callback);

        napi_value callbackArg[2] = {nullptr};
        operStatus =
            napi_create_string_utf8(env, message->value1.c_str(), message->value1.length(), &callbackArg[0]);
        if (operStatus != napi_ok) {
            return;
        }
                        
        ByteVector *buffer = message->buffer.get();
        char* chars = reinterpret_cast<char*> (buffer->data());
        
        char *data = nullptr;
        operStatus = napi_create_arraybuffer(env, buffer->size(), reinterpret_cast<void **>(&data), &callbackArg[1]);
        napi_create_buffer_copy(env, buffer->size(), chars, reinterpret_cast<void **>(&data), &callbackArg[1]);

        if (operStatus != napi_ok) {
            return;
        }

        napi_call_function(env, undefined, callback, 2, callbackArg, &ret);
    } else if (message->method == "quality") {
        // 回调ArkTS函数
        napi_value callback = nullptr;
        napi_get_reference_value(env, context2->qualityCallbackRef, &callback);

        napi_value callbackArg[2] = {nullptr};
        operStatus =
            napi_create_string_utf8(env, message->value1.c_str(), message->value1.length(), &callbackArg[0]);
        if (operStatus != napi_ok) {
            return;
        }

        operStatus = napi_create_int64(env, message->intValue1, &callbackArg[1]);
        if (operStatus != napi_ok) {
            return;
        }

        napi_call_function(env, undefined, callback, 2, callbackArg, &ret);
    } else if (message->method == "hangupRing") {
        PJSUAManager::getInstace()->StopHangupRing();
    }
}

static napi_value Init(napi_env env, napi_callback_info info) {
    size_t argc = 8;
    napi_value args[8] = {nullptr};

    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    bool enableUA;
    napi_get_value_bool(env, args[0], &enableUA);

    bool enableStun;
    napi_get_value_bool(env, args[1], &enableStun);

    int uniqueId = 6050;
    napi_get_value_int32(env, args[2], &uniqueId);

    auto asyncContext = new CallbackData();
    napi_create_reference(env, args[3], 1, &asyncContext->callbackRef);
    napi_create_reference(env, args[4], 1, &asyncContext->stateCallbackRef);
    napi_create_reference(env, args[5], 1, &asyncContext->recordCallbackRef);
    napi_create_reference(env, args[6], 1, &asyncContext->incomingCallbackRef);
    napi_create_reference(env, args[7], 1, &asyncContext->qualityCallbackRef);

    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, "Pjsip-Call-Back", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_threadsafe_function(env, nullptr, nullptr, resourceName, 0, 1, asyncContext, nullptr, asyncContext,
                                    CallJs, &asyncContext->tsfn);
    asyncContext->env = env;

    bool success = PJSUAManager::getInstace()->InitSDK(enableUA, enableStun, uniqueId, asyncContext);
    napi_value result;
    napi_create_int32(env, success ? 1 : 0, &result);
    return result;
}

static napi_value Register(napi_env env, napi_callback_info info) {
    size_t argc = 4;
    napi_value args[4] = {nullptr};

    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    char username[256];
    size_t username_length;
    napi_get_value_string_utf8(env, args[0], username, sizeof(username), &username_length);

    char password[256];
    size_t password_length;
    napi_get_value_string_utf8(env, args[1], password, sizeof(password), &password_length);

    char ip[256];
    size_t ip_length;
    napi_get_value_string_utf8(env, args[2], ip, sizeof(ip), &ip_length);

    char port[256];
    size_t port_length;
    napi_get_value_string_utf8(env, args[3], port, sizeof(port), &port_length);

    bool success = PJSUAManager::getInstace()->RegisterAccount(username, password, ip, port);

    napi_value result;
    napi_create_int32(env, success ? 1 : 0, &result);

    return result;
}

static napi_value MakeCall(napi_env env, napi_callback_info info) {
    size_t argc = 4;
    napi_value args[4] = {nullptr};

    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    char username[256];
    size_t username_length;
    napi_get_value_string_utf8(env, args[0], username, sizeof(username), &username_length);

    char displayNumber[256];
    size_t displayNumber_length;
    napi_get_value_string_utf8(env, args[1], displayNumber, sizeof(displayNumber), &displayNumber_length);

    char customWav[256];
    size_t customWav_length;
    napi_get_value_string_utf8(env, args[2], customWav, sizeof(customWav), &customWav_length);

    char xHeaders[256];
    size_t xHeaders_length;
    napi_get_value_string_utf8(env, args[3], xHeaders, sizeof(xHeaders), &xHeaders_length);

    yyjson_doc *doc = yyjson_read(xHeaders, strlen(xHeaders), 0);
    yyjson_val *root = yyjson_doc_get_root(doc);

    map<string, string> headers;

    size_t idx, max;
    yyjson_val *key, *val;
    yyjson_obj_foreach(root, idx, max, key, val) {
        headers.insert(pair<string, string>(yyjson_get_str(key), yyjson_get_str(val)));
    }

    auto sip_result = PJSUAManager::getInstace()->MakeCall(username, displayNumber, customWav, headers);

    yyjson_mut_doc *result_doc = yyjson_mut_doc_new(NULL);
    yyjson_mut_val *result_root = yyjson_mut_obj(result_doc);
    yyjson_mut_doc_set_root(result_doc, result_root);
    
    yyjson_mut_obj_add_str(result_doc, result_root, "call_id", sip_result.call_id.c_str());
    yyjson_mut_obj_add_str(result_doc, result_root, "error_msg", sip_result.error_msg.c_str());
    yyjson_mut_obj_add_bool(result_doc, result_root, "success", sip_result.success);

    const char *json = yyjson_mut_write(result_doc, 0, NULL);

    napi_value result;
    napi_create_string_utf8(env, json, strlen(json), &result);
    
    return result;
}

static napi_value Receive(napi_env env, napi_callback_info info) {
    bool success = PJSUAManager::getInstace()->Recive();
    napi_value result;
    napi_create_int32(env, success ? 1 : 0, &result);
    return result;
}

static napi_value Refuse(napi_env env, napi_callback_info info) {
    bool success = PJSUAManager::getInstace()->HangupCall();
    napi_value result;
    napi_create_int32(env, success ? 1 : 0, &result);
    return result;
}

static napi_value Mute(napi_env env, napi_callback_info info) {
    bool success = PJSUAManager::getInstace()->Mute();
    napi_value result;
    napi_create_int32(env, success ? 1 : 0, &result);
    return result;
}

static napi_value Logout(napi_env env, napi_callback_info info) {
    bool success = PJSUAManager::getInstace()->UnregisterAccount();
    napi_value result;
    napi_create_int32(env, success ? 1 : 0, &result);
    return result;
}

static napi_value Deinit(napi_env env, napi_callback_info info) {
    bool success = PJSUAManager::getInstace()->DeInitSDK();
    napi_value result;
    napi_create_int32(env, success ? 1 : 0, &result);
    return result;
}

static napi_value SendDTMF(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1] = {nullptr};

    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    char value[256];
    size_t value_length;
    napi_get_value_string_utf8(env, args[0], value, sizeof(value), &value_length);

    PJSUAManager::getInstace()->SendDTMF(value);
    napi_value result;
    napi_create_int32(env, 1, &result);
    return result;
}

static napi_value StopPlayer(napi_env env, napi_callback_info info) {
    PJSUAManager::getInstace()->StopPlayer();
    napi_value result;
    napi_create_int32(env, 1, &result);
    return result;
}

static napi_value StartRecord(napi_env env, napi_callback_info info) {
    PJSUAManager::getInstace()->StartRecord();
    napi_value result;
    napi_create_int32(env, 1, &result);
    return result;
}

static napi_value StopRecord(napi_env env, napi_callback_info info) {
    PJSUAManager::getInstace()->StopRecord();
    napi_value result;
    napi_create_int32(env, 1, &result);
    return result;
}

static napi_value StartRecordRemote(napi_env env, napi_callback_info info) {
    PJSUAManager::getInstace()->StartRecordRemote();
    napi_value result;
    napi_create_int32(env, 1, &result);
    return result;
}

static napi_value StopRecordRemote(napi_env env, napi_callback_info info) {
    PJSUAManager::getInstace()->StopRecordRemote();
    napi_value result;
    napi_create_int32(env, 1, &result);
    return result;
}

static napi_value CodecToggle(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1] = {nullptr};

    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    bool isOnlyUseG711;
    napi_get_value_bool(env, args[0], &isOnlyUseG711);

    PJSUAManager::getInstace()->UpdateCodecWithOnlyG711(isOnlyUseG711);
    napi_value result;
    napi_create_int32(env, 1, &result);
    return result;
}

static napi_value CreateWavPath(napi_env env, napi_callback_info info) {
    size_t argc = 3;
    napi_value args[3] = {nullptr};

    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    char ringPath[256];
    size_t ringPathLength;
    napi_get_value_string_utf8(env, args[0], ringPath, sizeof(ringPath), &ringPathLength);

    char hangupPath[256];
    size_t hangupPathLength;
    napi_get_value_string_utf8(env, args[1], hangupPath, sizeof(hangupPath), &hangupPathLength);

    char incomingPath[256];
    size_t incomingPathLength;
    napi_get_value_string_utf8(env, args[2], incomingPath, sizeof(incomingPath), &incomingPathLength);

    PJSUAManager::getInstace()->CreateWav(ringPath, hangupPath, incomingPath);
    napi_value result;
    napi_create_int32(env, 1, &result);
    return result;
}

static napi_value CheckLoopAudio(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1] = {nullptr};

    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    char recordPath[256];
    size_t recordPath_length;
    napi_get_value_string_utf8(env, args[0], recordPath, sizeof(recordPath), &recordPath_length);
    
    PJSUAManager::getInstace()->CheckLoopAudio(recordPath);

    napi_value result;
    napi_create_int32(env, 1, &result);
    return result;
}

static napi_value StopCheckLoopAudio(napi_env env, napi_callback_info info) {
    PJSUAManager::getInstace()->StopCheckLoopAudio();
    napi_value result;
    napi_create_int32(env, 1, &result);
    return result;
}


EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
    napi_property_descriptor desc[] = {
        {"init", nullptr, Init, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"makeCall", nullptr, MakeCall, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"register", nullptr, Register, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"receive", nullptr, Receive, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"refuse", nullptr, Refuse, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"mute", nullptr, Mute, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"logout", nullptr, Logout, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"deinit", nullptr, Deinit, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"sendDTMF", nullptr, SendDTMF, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"stopPlayer", nullptr, StopPlayer, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"startRecord", nullptr, StartRecord, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"stopRecord", nullptr, StopRecord, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"startRecordRemote", nullptr, StartRecordRemote, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"stopRecordRemote", nullptr, StopRecordRemote, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"codecToggle", nullptr, CodecToggle, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"createWavPath", nullptr, CreateWavPath, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"checkLoopAudio", nullptr, CheckLoopAudio, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"stopCheckLoopAudio", nullptr, StopCheckLoopAudio, nullptr, nullptr, nullptr, napi_default, nullptr},

    };
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "pjsip",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterPjsipModule(void) { napi_module_register(&demoModule); }
